package mastermind;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import mastermind.game.Game;
import mastermind.game.GameException;
import mastermind.game.Pieces;
import mastermind.game.Player;
import mastermind.game.Score;

public class DataOperator {

	public static final String DEFAULT_USER_LOG = "userlog.txt";
	public static final String DEFAULT_SCORE_LOG = "scorelog.txt";
	public static final String LOCAL_LOG = "localLog.txt";	
	public static final String SAVE_GAME_LOG_FORMAT = "saveGame-%s.txt";
	
	// path to data storage
	private String userLogPath, scoreLogPath;
	
	// all registered players
	private Map<String, Player> players;
	// scores for each player by user-name
	private Map<String, ArrayList<Score>> scores;

	
	/**
	 * The default constructor
	 */
	public DataOperator() {
		this(DEFAULT_USER_LOG, DEFAULT_SCORE_LOG);
	}
	
	/**
	 * Constructs a DataOperator object based on the userlog path, and the score path
	 * @param userLogPath
	 * @param scoreLogPath
	 */
	public DataOperator(String userLogPath, String scoreLogPath) {
		this.userLogPath = userLogPath;
		this.scoreLogPath = scoreLogPath;
		
		players = new HashMap<String, Player>();
		scores = new HashMap<String, ArrayList<Score>>();
		
		// if we have user details/scores, add these to the data structures before we continue
		init();
	}
	
	/**
	 * Internal method to initialise the data structures
	 */
	private void init() {

		File userlog = new File(userLogPath);
		File scorelog = new File(scoreLogPath);
		
		// check if the user log file exists
		if (userlog.exists()) {
			// the file is there
			try {
				Scanner scanner = new Scanner(userlog);
				while (scanner.hasNextLine()) {
					String userLine = scanner.nextLine();
					String[] userDetails = userLine.split(":", 5);
					//Create the player
					Player player = new Player(userDetails[0], userDetails[1],
							userDetails[2], userDetails[3], userDetails[4]);
					players.put(player.getUsername(), player);

					// give them an empty score list for now
					scores.put(player.getUsername(), new ArrayList<Score>());
				}
				scanner.close();
			} catch (Exception e) {
				// sadness
				e.printStackTrace();
			}
		}

		if (scorelog.exists()) {
			try {
				Scanner scanner = new Scanner(scorelog);
				while (scanner.hasNextLine()) {
					String line = scanner.nextLine();
					String[] userScore = line.split(":", 3);

					if (scores.containsKey(userScore[0])) {
						//we need to convert the format below, to that of a date type
						DateFormat format = new SimpleDateFormat("MM/dd/yyyy");
						Date d = format.parse(userScore[2]);
						Score score = new Score(Integer.parseInt(userScore[1]));
						
						score.setCompleted(d);
						scores.get(userScore[0]).add(score);
					}
				}
				scanner.close();
			} catch (Exception e) {
				// sadness
				e.printStackTrace();
			}

		}

	}

	/**
	 * Adds a player to the map
	 * @param player
	 * @return Boolean
	 */
	public synchronized boolean addPlayer(Player player) {
		System.out.println("attempt to register " + player.getUsername());

		scores.put(player.getUsername(), new ArrayList<Score>());

		players.put(player.getUsername(), player);

		System.out.println("Writing to file..");

		return writePlayer(player);
	}

	/**
	 * Used to fetch the player object specified by username
	 * 
	 * @param username
	 * @return Player
	 */
	public Player getPlayer(String username) {
		return players.get(username);
	}
	
	/**
	 * Internal method to write a player's details to file
	 * @param player
	 * @return Boolean
	 */
	private synchronized boolean writePlayer(Player player) {
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(userLogPath, true));
			String line = String.format("%s:%s:%s:%s:%s\n", player
					.getFirstname(), player.getSurname(), player.getUsername(),
					player.getPassword(), player.getPhoneNumber());
			bw.write(line);
			return true;
		} catch (IOException e) {
			// sadness
			e.printStackTrace();
		} finally {
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					// sadness
					e.printStackTrace();
				}
			}
		}
		return false;
	}
	
	/**
	 * Checks whether a user exists or not
	 * @param username
	 * @return Boolean
	 */
	public boolean userExists(String username) {
		return getPlayers().containsKey(username);
	}
	
	/**
	 * Adds a score based on a players username
	 * @param username
	 * @param score
	 * @return Boolean
	 */
	public synchronized boolean addScore(String username, Score score) {
		System.out.println("Attempting to add score for:"
				+ username);
		if (scores.containsKey(username)) {
			scores.get(username).add(score);
			// attempt to write it to the log
			return writeScore(username, score);
		}

		return false;
	}
	
	/**
	 * Internal method to write a players score to file
	 * @param username
	 * @param score
	 * @return
	 */
	private synchronized boolean writeScore(String username, Score score) {
		System.out.println("Attempting to write user score:"
				+ username);

		DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(scoreLogPath, true));
			String line = String.format("%s:%d:%s\n", username,
					score.getScore(), df.format(score.getDateCompleted()));
			bw.write(line);
			return true;
		} catch (IOException e) {
			// sadness
			e.printStackTrace();
		} finally {
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					// sadness
					e.printStackTrace();
				}
			}
		}
		return false;
	}
	
	/**
	 * Retrieves the current leader board
	 * @return HashMap
	 */
	public synchronized HashMap<String, Double> getLeaderBoard() {
		String username;
		double average;
		
		HashMap<String,Double> leaderBoard = new HashMap<String, Double>();
		Iterator<Player> player = players.values().iterator();
		
		//Create the temporary leader-board
		while(player.hasNext()) {
			username = player.next().getUsername();
			average = getPlayerAverage(username);
			leaderBoard.put(username, average);
		}
		return sortHashMap(leaderBoard);
	}
	
	/**
	 * Internal method used by getLeaderBoard() to sort in descending order 
	 * @param input
	 * @return HashMap
	 */
	private synchronized HashMap<String, Double> sortHashMap(HashMap<String, Double> input) {

		// create a temp map to store the values
		Map<String, Double> tempMap = new HashMap<String, Double>();

		for (String username : input.keySet()) {
			tempMap.put(username, input.get(username));
		}

		// create the two lists based on both fields for the map
		List<String> mapKeys = new ArrayList<String>(tempMap.keySet());
		List<Double> mapValues = new ArrayList<Double>(tempMap.values());

		// use the built in sort
		Collections.sort(mapKeys);
		Collections.sort(mapValues);
		// we want descending
		Collections.reverse(mapValues);

		// will hold the final leader-board
		LinkedHashMap<String, Double> leaderBoard = new LinkedHashMap<String, Double>();

		Iterator<Double> valueItr = mapValues.iterator();
		while (valueItr.hasNext()) {
			// get the value
			Object val = valueItr.next();
			
			Iterator<String> keyItr = mapKeys.iterator();
			while (keyItr.hasNext()) {
				Object key = keyItr.next();
				// if they are the same, then we should remove the item from the
				// map
				if (input.get(key).toString().equals(val.toString())) {
					input.remove(key);
					mapKeys.remove(key);
					leaderBoard.put((String) key, (Double) val);
					break;
				}
			}
		}
		return leaderBoard;
	}
	
	/**
	 * Retrieves the overall average 
	 * @return Double
	 */
	public synchronized double getAverageScore() {
		double totalScore = 0;
		int totalGames = 0;
		// start an iterator for the arraylist array
		Iterator<ArrayList<Score>> allScores = scores.values().iterator();
		while (allScores.hasNext()) {
			// set up the iterator for each arraylist at each index
			Iterator<Score> playerScores = allScores.next().iterator();
			while (playerScores.hasNext()) {
				totalScore += playerScores.next().getScore();
				totalGames++;
			}
		}
		return totalScore / totalGames;
	}
	
	/**
	 * Returns the total amount of games across all users
	 * @return int
	 */
	public synchronized int getTotalGames() {
		int totalGames = 0;
		Iterator<ArrayList<Score>> allScores = scores.values().iterator();
		while (allScores.hasNext()) {
			Iterator<Score> playerScores = allScores.next().iterator();
			while (playerScores.hasNext()) {
				totalGames++;
			}
		}
		return totalGames;
	}

	/**
	 * Used to fetch the supplied users average
	 * 
	 * @param username
	 * @return Double, the player's average
	 */
	public synchronized double getPlayerAverage(String username) {
		double currentScore = 0;
		if (scores.get(username).size() != 0) {
			Iterator<Score> playerScores = scores.get(username).iterator();
			while (playerScores.hasNext()) {
				currentScore += playerScores.next().getScore();
			}
			return currentScore /= scores.get(username).size();
		}

		return currentScore;
	}

	/**
	 * Used to get the current map of all the players scores
	 * 
	 * @return Map
	 */
	public Map<String, ArrayList<Score>> getScores() {
		return scores;
	}

	/**
	 * Used to get the current map of players
	 * 
	 * @return Map
	 */
	public Map<String, Player> getPlayers() {
		return players;
	}
	
	public Game getSavedGame(String username) {
		File f = new File(String.format(SAVE_GAME_LOG_FORMAT, username));

		if (f.exists()) {
			try {
				Game g;
				Scanner scanner = new Scanner(f);
				// get the secret, the only mandatory part of the save game file
				if (scanner.hasNextLine()) {
					String secret = scanner.nextLine();
					g = new Game(new Pieces(secret.toCharArray()));
				} else {
					return null;
				}
				// get any guesses so far
				while (scanner.hasNextLine()) {
					String guess = scanner.nextLine();
					try {
						g.makeGuess(new Pieces(guess.toCharArray()));
					} catch (GameException e) {
						System.err.println("getSavedGame: " + e.getMessage());
					}
				}
				scanner.close();
				return g;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	public synchronized void saveGame(String username, Game g) {
		File f = new File(String.format(SAVE_GAME_LOG_FORMAT, username));

		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(f));
			// first line in game log is secret
			bw.write(g.getSecret().toString() + "\n");
			// then, each guess so far
			Map<Pieces, String> guesses = g.getGuesses();
			for (Pieces p : guesses.keySet()) {
				bw.write(String.format("%s\n", p.toString()));
			}
		} catch (IOException e) {
			// sadness
			e.printStackTrace();
		} finally {
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					// sadness
					e.printStackTrace();
				}
			}
		}
	}
	public boolean hasSaveGame(String username) {
		File f = new File(String.format(SAVE_GAME_LOG_FORMAT, username));
		return f.exists();
	}
	
	public synchronized void deleteSaveGame(String username) {
		File f = new File(String.format(SAVE_GAME_LOG_FORMAT, username));
		if(f.exists()) {
			f.delete();
		}
	}
}
